{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 程序说明\n",
    "时间：2016年11月17日\n",
    "\n",
    "名称：迁移学习例程\n",
    "\n",
    "说明：该程序演示将一个预训练好的模型在新数据集上重新fine-tuning的过程。我们冻结卷积层，只调整全连接层。\n",
    "1. 在MNIST数据集上使用前五个数字[0...4]训练一个卷积网络。\n",
    "2. 在后五个数字[5...9]用卷积网络做分类，冻结卷积层并且微调全连接层\n",
    "\n",
    "数据集：MNIST"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.加载keras模块"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "from __future__ import print_function\n",
    "import numpy as np\n",
    "import datetime\n",
    "\n",
    "np.random.seed(1337)  # for reproducibility\n",
    "\n",
    "from keras.datasets import mnist\n",
    "from keras.models import Sequential\n",
    "from keras.layers import Dense, Dropout, Activation, Flatten\n",
    "from keras.layers import Convolution2D, MaxPooling2D\n",
    "from keras.utils import np_utils\n",
    "from keras import backend as K"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.变量初始化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "now = datetime.datetime.now\n",
    "\n",
    "batch_size = 128\n",
    "nb_classes = 5\n",
    "nb_epoch = 5\n",
    "\n",
    "# 输入图像的维度\n",
    "img_rows, img_cols = 28, 28\n",
    "# 使用卷积滤波器的数量\n",
    "nb_filters = 32\n",
    "# 用于max pooling的pooling面积的大小\n",
    "pool_size = 2\n",
    "# 卷积核的尺度\n",
    "kernel_size = 3\n",
    "\n",
    "if K.image_dim_ordering() == 'th':\n",
    "    input_shape = (1, img_rows, img_cols)\n",
    "else:\n",
    "    input_shape = (img_rows, img_cols, 1)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3.准备数据\n",
    "将MNIST划分为两个数据集，分别用于预训练模型和迁移学习模型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 数据，在训练和测试数据集上混洗和拆分\n",
    "(X_train, y_train), (X_test, y_test) = mnist.load_data()\n",
    "\n",
    "# create two datasets one with digits below 5 and one with 5 and above\n",
    "X_train_lt5 = X_train[y_train < 5]\n",
    "y_train_lt5 = y_train[y_train < 5]\n",
    "X_test_lt5 = X_test[y_test < 5]\n",
    "y_test_lt5 = y_test[y_test < 5]\n",
    "\n",
    "X_train_gte5 = X_train[y_train >= 5]\n",
    "y_train_gte5 = y_train[y_train >= 5] - 5  # make classes start at 0 for\n",
    "X_test_gte5 = X_test[y_test >= 5]         # np_utils.to_categorical\n",
    "y_test_gte5 = y_test[y_test >= 5] - 5"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 模型的训练函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def train_model(model, train, test, nb_classes):\n",
    "    X_train = train[0].reshape((train[0].shape[0],) + input_shape)\n",
    "    X_test = test[0].reshape((test[0].shape[0],) + input_shape)\n",
    "    X_train = X_train.astype('float32')\n",
    "    X_test = X_test.astype('float32')\n",
    "    X_train /= 255\n",
    "    X_test /= 255\n",
    "    print('X_train shape:', X_train.shape)\n",
    "    print(X_train.shape[0], 'train samples')\n",
    "    print(X_test.shape[0], 'test samples')\n",
    "\n",
    "    # convert class vectors to binary class matrices\n",
    "    Y_train = np_utils.to_categorical(train[1], nb_classes)\n",
    "    Y_test = np_utils.to_categorical(test[1], nb_classes)\n",
    "\n",
    "    model.compile(loss='categorical_crossentropy',\n",
    "                  optimizer='adadelta',\n",
    "                  metrics=['accuracy'])\n",
    "\n",
    "    t = now()\n",
    "    model.fit(X_train, Y_train,\n",
    "              batch_size=batch_size, nb_epoch=nb_epoch,\n",
    "              verbose=1,\n",
    "              validation_data=(X_test, Y_test))\n",
    "    print('Training time: %s' % (now() - t))\n",
    "    score = model.evaluate(X_test, Y_test, verbose=0)\n",
    "    print('Test score:', score[0])\n",
    "    print('Test accuracy:', score[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.建立模型\n",
    "### 构建卷积层（特征层）和全连接层（分类层）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# define two groups of layers: feature (convolutions) and classification (dense)\n",
    "feature_layers = [\n",
    "    Convolution2D(nb_filters, kernel_size, kernel_size,\n",
    "                  border_mode='valid',\n",
    "                  input_shape=input_shape),\n",
    "    Activation('relu'),\n",
    "    Convolution2D(nb_filters, kernel_size, kernel_size),\n",
    "    Activation('relu'),\n",
    "    MaxPooling2D(pool_size=(pool_size, pool_size)),\n",
    "    Dropout(0.25),\n",
    "    Flatten(),\n",
    "]\n",
    "classification_layers = [\n",
    "    Dense(128),\n",
    "    Activation('relu'),\n",
    "    Dropout(0.5),\n",
    "    Dense(nb_classes),\n",
    "    Activation('softmax')\n",
    "]\n",
    "\n",
    "# create complete model\n",
    "model = Sequential(feature_layers + classification_layers)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5.训练预训练模型用于5个数字[0...4]的分类任务"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "X_train shape: (30596, 28, 28, 1)\n",
      "30596 train samples\n",
      "5139 test samples\n",
      "Train on 30596 samples, validate on 5139 samples\n",
      "Epoch 1/5\n",
      "30596/30596 [==============================] - 5s - loss: 0.2148 - acc: 0.9349 - val_loss: 0.0398 - val_acc: 0.9868\n",
      "Epoch 2/5\n",
      "30596/30596 [==============================] - 4s - loss: 0.0631 - acc: 0.9810 - val_loss: 0.0234 - val_acc: 0.9924\n",
      "Epoch 3/5\n",
      "30596/30596 [==============================] - 4s - loss: 0.0457 - acc: 0.9869 - val_loss: 0.0192 - val_acc: 0.9926\n",
      "Epoch 4/5\n",
      "30596/30596 [==============================] - 4s - loss: 0.0367 - acc: 0.9885 - val_loss: 0.0150 - val_acc: 0.9946\n",
      "Epoch 5/5\n",
      "30596/30596 [==============================] - 4s - loss: 0.0304 - acc: 0.9911 - val_loss: 0.0157 - val_acc: 0.9947\n",
      "Training time: 0:00:24.128250\n",
      "Test score: 0.0156876081334\n",
      "Test accuracy: 0.994746059545\n"
     ]
    }
   ],
   "source": [
    "# train model for 5-digit classification [0..4]\n",
    "train_model(model,\n",
    "            (X_train_lt5, y_train_lt5),\n",
    "            (X_test_lt5, y_test_lt5), nb_classes)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.冻结特征层"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# freeze feature layers and rebuild model\n",
    "for l in feature_layers:\n",
    "    l.trainable = False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7.fine-tuning分类层"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "X_train shape: (29404, 28, 28, 1)\n",
      "29404 train samples\n",
      "4861 test samples\n",
      "Train on 29404 samples, validate on 4861 samples\n",
      "Epoch 1/5\n",
      "29404/29404 [==============================] - 3s - loss: 0.3601 - acc: 0.8942 - val_loss: 0.0906 - val_acc: 0.9704\n",
      "Epoch 2/5\n",
      "29404/29404 [==============================] - 2s - loss: 0.1231 - acc: 0.9625 - val_loss: 0.0584 - val_acc: 0.9809\n",
      "Epoch 3/5\n",
      "29404/29404 [==============================] - 3s - loss: 0.0917 - acc: 0.9732 - val_loss: 0.0480 - val_acc: 0.9837\n",
      "Epoch 4/5\n",
      "29404/29404 [==============================] - 3s - loss: 0.0753 - acc: 0.9777 - val_loss: 0.0387 - val_acc: 0.9856\n",
      "Epoch 5/5\n",
      "29404/29404 [==============================] - 3s - loss: 0.0654 - acc: 0.9805 - val_loss: 0.0347 - val_acc: 0.9875\n",
      "Training time: 0:00:15.838667\n",
      "Test score: 0.0347462616509\n",
      "Test accuracy: 0.98745114163\n"
     ]
    }
   ],
   "source": [
    "# transfer: train dense layers for new classification task [5..9]\n",
    "train_model(model,\n",
    "            (X_train_gte5, y_train_gte5),\n",
    "            (X_test_gte5, y_test_gte5), nb_classes)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.12"
  },
  "ssap_exp_config": {
   "error_alert": "Error Occurs!",
   "initial": [],
   "max_iteration": 1000,
   "recv_id": "",
   "running": [],
   "summary": [],
   "version": "1.1.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
